import webapp2
import json
import urllib2
import re
from google.appengine.ext import db
from google.appengine.api import users
from google.appengine.api import urlfetch
from google.appengine.api import memcache


ERR_SNI  = '{"error": "System has not been initialised"}'
ERR_NSF  = '{"error": "%s: No such file or directory"}'
ERR_MEW  = '{"error": "%s: Directory must end with /"}'
ERR_CEW  = '{"error": "%s: File cannot end with /"}'
ERR_DE   = '{"error": "%s: Directory already exists"}'
ERR_FE   = '{"error": "%s: File already exists"}'
ERR_PD   = '{"error": "%s: Permission denied"}'

RESOURCE_REQUEST_RE = re.compile('\/.*\..*$')
INDEX_REQUEST_RE = re.compile('\/$|^$')
IMAGE_RE = re.compile('(jpeg|jpg|png|gif)$')

class File(db.Model): 
    metadata = db.StringProperty()
    content = db.TextProperty()



class App(webapp2.RequestHandler):
    
    
    def get(self):
        
        try:
            self.init()
            self.options()
            
            if self.message is not None:
                return self.response.out.write(self.message)
            
            if self.path.startswith('/tmp/'):
                file = memcache.get(self.path)
            
            elif self.path.startswith('/proxy/'):
                url = 'http://' + urllib2.unquote(self.path[7:])
                if IMAGE_RE.search(url):
                    self.response.headers['Content-Type'] = 'image/%s' % url[url.rfind('.')+1:].replace('jpg', 'jpeg')
                else:
                    self.response.headers['Content-Type'] = 'text/html'
                self.urlfetch(url)
                return self.response.out.write(self.message)
            
            elif not RESOURCE_REQUEST_RE.search(self.path):
                tmp = self.path
                self.path = '/oj/oj.html'
                file = File.get_by_key_name(self.path)
                if file is not None:
                    self.path = tmp
                    html = file.content
                    file = None
                    if INDEX_REQUEST_RE.search(self.path):
                        if self.path == '':
                            self.path = '/__dir__'
                        else:
                            self.path = '%s__dir__' % self.path
                    self.path += '.js'
                    file = File.get_by_key_name(self.path)
                    if file is not None:
                        file = html.replace('/*__MAIN__*/', "require(['%s']);" % self.path[1:].replace('.js', ''))
                    self.response.headers['Content-Type'] = 'text/html'
            
            else:
                file = File.get_by_key_name(self.path)
                if file is not None:
                    file = file.content
            
            if file is None:
                self.reply(ERR_NSF % self.path, dump=False)
                return self.response.out.write(self.message)
            
            self.reply(file.encode('utf-8'), dump=False)
            
            if self.path.endswith('.html'):
                self.response.headers['Content-Type'] = 'text/html'
            elif self.path.endswith( '.css' ):
                self.response.headers['Content-Type'] = 'text/css'
            elif self.path.endswith( 'rss.xml' ):
                self.response.headers['Content-Type'] = 'application/rss+xml'
            elif self.path.endswith( '.xml' ):
                self.response.headers['Content-Type'] = 'text/xml'
            
            self.response.out.write(self.message)
        
        except Exception, e:
            self.response.out.write('{"error": "%s"}' % e.message)
    
    
    
    def init(self):
        self.response.headers['Content-Type'] = 'application/javascript'
        self.jsonp = None
        self.message = None
        self.user = users.get_current_user()
        self.path = self.request.path
    
    
    
    def options(self):
        for arg in self.request.query_string.split('&'):
            
            if arg.startswith('P/'):
                self.jsonp = urllib2.unquote(arg[2:])
            
            # I/ - Identify
            if arg.startswith('I/'):
                if File.get_by_key_name('/user/') is None:
                    return self.reply(ERR_SNI, dump=False)
                if self.user is not None:
                    path = '/user/%s/' % self.user.nickname()
                    dir = File.get_by_key_name(path)
                    if dir is None:
                        dir = File(key_name=path)
                        dir.metadata = json.dumps({'user': self.user.user_id()})
                        dir.content = json.dumps([])
                        dir.put()
                        db.run_in_transaction(self.attach, path)
                    return self.reply({'name': self.user.nickname(),
                                       'email': self.user.email()})
                return self.reply({'login': users.create_login_url(arg[2:])})
            
            # E/ - Logout
            if arg.startswith('E/'):
                return self.reply({'logout': users.create_logout_url(arg[2:])})
            
            # Z/ - Format file system
            if arg.startswith('Z/'):
                if self.user is None:
                    return self.reply({'login': users.create_login_url(arg[2:])})
                
                if not users.is_current_user_admin():
                    return self.reply({'error': 'only administrators can initialise the system'})
                
                self.rmdir('/')
                
                root = File(key_name='/')
                root.metadata = json.dumps({'user': self.user.user_id()})
                root.content = json.dumps([])
                root.put()
                
                self.mkdir('/user/')
                
                return self.reply({'success': 'Format successful'})
            
            # M/ - Make directory
            if arg.startswith('M/'):
                return self.mkdir(self.path)
            
            # N/ - Move file
            if arg.startswith('N/'):
                return self.move(self.path, self.abspath(arg[2:]))
            
            # X/ - Delete directory 
            if arg.startswith('X/'):
                return self.rmdir(self.path)
            
            # W/ - Write to file
            if arg.startswith('W/'):
                return self.write(self.path, urllib2.unquote(arg[2:]).decode('utf-8'))
            
            # D/ - Delete file
            if arg.startswith('D/'):
                return self.delete(self.path)
            
            # R/ - Read directory or file
            if arg.startswith('R/'):
                return self.read(self.path)
            
            # C/ - Concatenate files
            if arg.startswith('C'):
                paths = []
                clean = False
                if arg.startswith('CR/'):
                    clean = True
                    paths.extend(urllib2.unquote(arg[3:]).split('!'))
                else:
                    paths.extend(urllib2.unquote(arg[2:]).split('!'))
                paths.append(self.path)
                self.concat(paths, clean)
            
    
    
    
    def reply(self, message, dump=True):
        
        if dump:
            message = json.dumps(message)
        
        if self.jsonp is not None:
            self.message = '%s(%s)' % (self.jsonp, message)
        else:
            self.message = message
    
    
    
    def abspath(self, arg):
        path = urllib2.unquote(arg)
        
        if path.startswith('/'):
            return path
        
        return '/%s' % path
    
    
    
    def basename(self, path):
        base = '%s'
        
        if path.endswith('/'):
            path = path[:-1]
            base = '%s/'
            
        if path == '':
            return None
        
        return base % path.split('/')[-1]
    
    
    
    def dirname(self, path):
        if path.endswith('/'):
            path = path[:-1]
        
        if path == '':
            return None
        
        return '%s/' % '/'.join(path.split('/')[:-1])
    
    
    
    def attach(self, path):
        parent_path = self.dirname(path)
        child_path = self.basename(path)
        
        parent_dir = File.get_by_key_name(parent_path)
        parent_content = json.loads(parent_dir.content)
        
        if not child_path in parent_content:
            parent_content.append(child_path)
        
        parent_dir.content = json.dumps(parent_content)
        parent_dir.put()
    
    
    
    def detach(self, path):
        if path == '/':
            return
        
        file = self.basename(path)
        path = self.dirname(path)
        dir = File.get_by_key_name(path)
        
        if dir is not None:
            content = json.loads(dir.content)
            content.remove(file)
            dir.content = json.dumps(content)
            dir.put()
    
    
    
    def delete(self, path):
        if path.endswith('/'):
            self.reply(ERR_CEW % path, dump=False)
            return False
        
        if path.startswith('/tmp/'):
            if memcache.delete(path) == 2:
                return True
            else:
                return False
        
        file = File.get_by_key_name(path)
        if file is None:
            self.reply(ERR_NSF % path)
            return False
        
        
        if json.loads(file.metadata)['user'] != self.user.user_id():
            self.reply(ERR_PD % path, dump=False)
            return False
        
        db.run_in_transaction(self.detach, path)
        file.delete()
        
        self.reply( { 'success': 'File deleted' } )
        return True
    
    
    
    def rmdir(self, path):
        if path.startswith('/tmp/'):
            self.reply(ERR_PD % path, dump=False)
            return False
        
        if not path.endswith('/'):
            self.reply(ERR_MEW % path, dump=False)
            return False
        
        dir = File.get_by_key_name(path)
        
        if dir is None:
            self.reply(ERR_NSF % path, dump=False)
            return False
        
        if json.loads(dir.metadata)['user'] != self.user.user_id():
            self.reply(ERR_PD % path, dump=False)
            return False
        
        dirs = []
        
        while len(dirs) > 0 or dir is not None:
            if dir is not None:
                for file in json.loads(dir.content):
                    if file.endswith('/'):
                        dirs.append('%s%s' % (path, file))
                    else:
                        file = File.get_by_key_name('%s%s' % (path, file))
                        if file is not None:
                            file.delete()
                dir.delete()
        
            if len(dirs) == 0:
                dir = None
            else:
                dir = File.get_by_key_name(dirs.pop())
        
        db.run_in_transaction(self.detach, path)
        
        self.reply({'success': 'Directory removed'})
        return True
    
    
    
    
    def mkdir(self, path):
        if path.startswith('/tmp/'):
            self.reply(ERR_PD % path, dump=False)
            return False
        
        if not self.user:
            self.reply(ERR_PD % path, dump=False)
            return False
        
        if not path.endswith('/'):
            self.reply(ERR_MEW % path, dump=False)
            return False
        
        dir = File.get_by_key_name(path)
        
        if not dir is None:
            self.reply(ERR_DE % path, dump=False)
            return False
        
        parent_path = self.dirname(path)
        parent_dir = File.get_by_key_name(parent_path)
        
        if parent_dir is None:
            self.reply(ERR_NSF % parent_path, dump=False)
            return False
        
        if json.loads(parent_dir.metadata)['user'] != self.user.user_id():
            self.reply(ERR_PD % parent_path, dump=False)
            return False
        
        dir = File(key_name=path)
        dir.metadata = json.dumps({'user': self.user.user_id()})
        dir.content = json.dumps([])
        dir.put()
        
        db.run_in_transaction(self.attach, path)
        
        self.reply({'success': 'Directory created'})
        return True
    
    
    
    def move(self, src, dst, detach=True):
        if dst.startswith('/tmp/'):
            self.reply(ERR_PD % dst, dump=False)
            return False
        
        if src.startswith('/tmp/'):
            src_file = memcache.get(src)
        else:
            src_file = File.get_by_key_name(src)
        
        if src_file is None:
            self.reply(ERR_NSF % src, dump=False)
            return False
        
        dst_file = File.get_by_key_name(dst)
        
        if dst_file is not None:
            self.reply(ERR_FE % dst, dump=False)
            return False
        
        if not src.startswith('/tmp/') and json.loads(src_file.metadata )['user'] != self.user.user_id():
            self.reply(ERR_PD % src, dump=False)
            return False
        
        dst_parent_path = self.dirname(dst)
        dst_parent_dir = File.get_by_key_name(dst_parent_path)
        
        if dst_parent_dir is None:
            self.reply(ERR_NSF % dst_parent_path, dump=False)
            return False
        
        if json.loads(dst_parent_dir.metadata)['user'] != self.user.user_id():
            self.reply(ERR_PD % dst_parent_path, dump=False)
            return False
        
        def write(path, content):
            file = File(key_name=path)
            file.metadata = json.dumps({'user': self.user.user_id()})
            file.content = content
            file.put()
        
        if dst.startswith('/tmp/'):
            db.run_in_transaction(write, dst, src_file)
        else:
            db.run_in_transaction(write, dst, src_file.content)
        
        type = 'File'
        if src.endswith('/'):
            type = 'Directory'
            for child in json.loads(src_file.content):
                self.move('%s%s' % (src, child), '%s%s' % (dst, child), False)
        
        if dst.startswith('/tmp/'):
            memcache.delete(src)
        else:
            if detach:
                db.run_in_transaction(self.attach, dst)
                db.run_in_transaction(self.detach, src)
            
            src_file.delete()
        
        self.reply({'success': '%s moved' % type})
        return True
    
    
    
    def write(self, path, content, dump=False):
        if not self.user:
            self.reply(ERR_PD % path, dump=False)
            return False
        
        if path.endswith('/'):
            self.reply(ERR_CEW % path)
            return False
        
        if dump:
            content = json.dumps(content)
        
        
        if path.startswith('/tmp/'):
            if memcache.set(path, content):
                self.reply({'success': path})
                return True
            else:
                self.reply({'error': path})
                return False
        
        file = File.get_by_key_name(path)
        
        if file is None:
            parent_path = self.dirname(path)
            parent = File.get_by_key_name(parent_path)
            
            if parent is None:
                self.reply(ERR_NSF % parent_path, dump=False)
                return False
            
            if json.loads(parent.metadata)['user'] != self.user.user_id():
                self.reply(ERR_PD % parent_path, dump=False)
                return False
            
            def write():
                file = File(key_name=path)
                file.metadata = json.dumps({'user': self.user.user_id()})
                file.content = content
                file.put()
            
            db.run_in_transaction(write)
            db.run_in_transaction(self.attach, path)
        
        else:
            if json.loads(file.metadata)['user'] != self.user.user_id():
                self.reply(ERR_PD % path, dump=False)
                return False
            
            file.content = content
            file.put()
        
        self.reply({'success': path})
        return True
    
    
    
    def read(self, path):
        if path.startswith('/tmp/'):
            file = memcache.get(path)
        else:
            file = File.get_by_key_name(path)
            if file is not None:
                file = file.content
        
        if file is None:
            self.reply(ERR_NSF % path, dump=False)
            return False
        
        self.reply(file, dump=self.jsonp is not None)
        return True
    
    
    
    def concat(self, paths, clean=False):
        if not self.user:
            self.reply(ERR_PD % paths[-1], dump=False)
            return False
        
        content = ''
        
        for path in paths[:-1]:
            if path.startswith('/tmp/'):
                file = memcache.get(path)
                
                if file is None:
                    self.reply(ERR_NSF % path)
                    return False
                
                content += file
                
                if clean:
                    memcache.delete(path)
            
            else:
                file = File.get_by_key_name(path)
                
                if file is None:
                    self.reply(ERR_NSF % path)
                    return False
                
                if json.loads(file.metadata)['user'] != self.user.user_id():
                    self.reply(ERR_PD % path, dump=False)
                    return False
                
                content += file.content
                
                if clean:
                    file.delete()
                    db.run_in_transaction(self.detach, path)
        
        if paths[-1].endswith('/'):
            paths[-1] = paths[-1] + self.basename(path)
        
        self.write(paths[-1], content)
        
        self.reply({'success': 'Successfully concatenated'})
        return True
    
    
    
    def urlfetch(self, url):
        if not self.user:
            self.reply(ERR_PD % url, dump=False)
            return False
        
        resource = memcache.get(url)
        if resource is None:
            resource = urlfetch.fetch(url=url, deadline=10).content
            memcache.add(url, resource, time=120)
        
        self.reply(resource, dump=False)
        return True



app = webapp2.WSGIApplication([('/.*', App)], debug=True)
